#ifndef S21_CONTAINER_ARRAY_H_
#define S21_CONTAINER_ARRAY_H_

#include <initializer_list>
#include <stdexcept>

namespace s21 {

template <typename T, std::size_t N>
class array {
 public:
  using value_type = T;
  using reference = value_type&;
  using const_reference = const value_type&;
  using iterator = value_type*;
  using const_iterator = const value_type*;
  using size_type = std::size_t;

  array() {
    for (size_type i = 0; i < N; ++i) {
      new (data_ + i) T();
    }
  }

  array(std::initializer_list<value_type> const& items) {
    size_type sizes_min = N < items.size() ? N : items.size();
    auto it = items.begin();

    for (size_type i = 0; i < sizes_min; ++i) {
      new (data_ + i) T(*it);
      ++it;
    }

    for (size_type i = sizes_min; i < N; ++i) {
      new (data_ + i) T();
      ++it;
    }
  }

  array(const array& other) {
    size_type i = 0;

    for (auto it = other.cbegin(); it != other.cend(); ++it) {
      new (data_ + i) T(*it);
      i++;
    }
  }

  array(array&& other) noexcept {
    size_type i = 0;

    for (auto it = other.begin(); it != other.end(); ++it) {
      new (data_ + i) T(std::move(*it));
      i++;
    }
  }

  ~array() {
    for (size_type i = 0; i < N; ++i) {
      (data_ + i)->~T();
    }
  }

  array& operator=(const array& other) {
    if (this != &other) {
      array tmp{other};
      swap(tmp);
    }

    return *this;
  }

  array& operator=(array&& other) noexcept {
    if (this != &other) {
      array tmp{std::move(other)};
      swap(tmp);
    }

    return *this;
  }

  reference at(size_type index) {
    if (index >= N) {
      throw std::out_of_range("array.at: index out of range.");
    }

    return data_[index];
  }

  const_reference at(size_type index) const {
    if (index >= N) {
      throw std::out_of_range("array.at: index out of range.");
    }

    return data_[index];
  }

  reference operator[](size_type index) { return data_[index]; }

  const_reference operator[](size_type index) const { return data_[index]; }

  const_reference front() const { return data_[0]; }

  const_reference back() const { return data_[N - 1]; }

  iterator begin() { return data_; }

  iterator end() { return data_ + N; }

  const_iterator cbegin() const { return data_; }

  const_iterator cend() const { return data_ + N; }

  iterator data() { return begin(); }

  bool empty() const noexcept { return N == 0; }

  size_type size() const noexcept { return N; }

  size_type max_size() const { return N; }

  void swap(array& other) noexcept {
    std::swap_ranges(begin(), end(), other.begin());
  }

  void fill(const_reference value) {
    for (size_type i = 0; i < N; ++i) {
      (data_ + i)->~T();
      new (data_ + i) T(value);
    }
  }

 private:
  uint8_t pool_[N * sizeof(value_type) + 1];
  value_type* data_ = reinterpret_cast<value_type*>(pool_);
};
}  // namespace s21
#endif  // S21_CONTAINER_ARRAY_H_